import Url from "./utils/url";

/**
 * Themes to apply to displayed content
 * @class
 * @param {Rendition} rendition
 */
class Themes {
  constructor(rendition) {
    this.rendition = rendition;
    this._themes = {
      default: {
        rules: {},
        url: "",
        serialized: "",
      },
    };
    this._overrides = {};
    this._current = "default";
    this._injected = [];
    this.rendition.hooks.content.register(this.inject.bind(this));
    this.rendition.hooks.content.register(this.overrides.bind(this));
  }

  /**
   * Add themes to be used by a rendition
   * @param {object | Array<object> | string}
   * @example themes.register("light", "http://example.com/light.css")
   * @example themes.register("light", { "body": { "color": "purple"}})
   * @example themes.register({ "light" : {...}, "dark" : {...}})
   */
  register() {
    if (arguments.length === 0) {
      return;
    }
    if (arguments.length === 1 && typeof arguments[0] === "object") {
      return this.registerThemes(arguments[0]);
    }
    if (arguments.length === 1 && typeof arguments[0] === "string") {
      return this.default(arguments[0]);
    }
    if (arguments.length === 2 && typeof arguments[1] === "string") {
      return this.registerUrl(arguments[0], arguments[1]);
    }
    if (arguments.length === 2 && typeof arguments[1] === "object") {
      return this.registerRules(arguments[0], arguments[1]);
    }
  }

  /**
   * Add a default theme to be used by a rendition
   * @param {object | string} theme
   * @example themes.register("http://example.com/default.css")
   * @example themes.register({ "body": { "color": "purple"}})
   */
  default(theme) {
    if (!theme) {
      return;
    }
    if (typeof theme === "string") {
      return this.registerUrl("default", theme);
    }
    if (typeof theme === "object") {
      return this.registerRules("default", theme);
    }
  }

  /**
   * Register themes object
   * @param {object} themes
   */
  registerThemes(themes) {
    for (var theme in themes) {
      if (themes.hasOwnProperty(theme)) {
        if (typeof themes[theme] === "string") {
          this.registerUrl(theme, themes[theme]);
        } else {
          this.registerRules(theme, themes[theme]);
        }
      }
    }
  }

  /**
   * Register a url
   * @param {string} name
   * @param {string} input
   */
  registerUrl(name, input) {
    var url = new Url(input);
    this._themes[name] = { url: url.toString() };
    if (this._injected[name]) {
      this.update(name);
    }
  }

  /**
   * Register rule
   * @param {string} name
   * @param {object} rules
   */
  registerRules(name, rules) {
    this._themes[name] = { rules: rules };
    // TODO: serialize css rules
    if (this._injected[name]) {
      this.update(name);
    }
  }

  /**
   * Select a theme
   * @param {string} name
   */
  select(name) {
    var prev = this._current;
    var contents;

    this._current = name;
    this.update(name);

    contents = this.rendition.getContents();
    contents.forEach((content) => {
      content.removeClass(prev);
      content.addClass(name);
    });
  }

  /**
   * Update a theme
   * @param {string} name
   */
  update(name) {
    var contents = this.rendition.getContents();
    contents.forEach((content) => {
      this.add(name, content);
    });
  }

  /**
   * Inject all themes into contents
   * @param {Contents} contents
   */
  inject(contents) {
    var links = [];
    var themes = this._themes;
    var theme;

    for (var name in themes) {
      if (
        themes.hasOwnProperty(name) &&
        (name === this._current || name === "default")
      ) {
        theme = themes[name];
        if (
          (theme.rules && Object.keys(theme.rules).length > 0) ||
          (theme.url && links.indexOf(theme.url) === -1)
        ) {
          this.add(name, contents);
        }
        this._injected.push(name);
      }
    }

    if (this._current != "default") {
      contents.addClass(this._current);
    }
  }

  /**
   * Add Theme to contents
   * @param {string} name
   * @param {Contents} contents
   */
  add(name, contents) {
    var theme = this._themes[name];

    if (!theme || !contents) {
      return;
    }

    if (theme.url) {
      contents.addStylesheet(theme.url);
    } else if (theme.serialized) {
      // TODO: handle serialized
    } else if (theme.rules) {
      contents.addStylesheetRules(theme.rules);
      theme.injected = true;
    }
  }

  /**
   * Add override
   * @param {string} name
   * @param {string} value
   * @param {boolean} priority
   */
  override(name, value, priority) {
    var contents = this.rendition.getContents();

    this._overrides[name] = {
      value: value,
      priority: priority === true,
    };

    contents.forEach((content) => {
      content.css(
        name,
        this._overrides[name].value,
        this._overrides[name].priority
      );
    });
  }

  removeOverride(name) {
    var contents = this.rendition.getContents();

    delete this._overrides[name];

    contents.forEach((content) => {
      content.css(name);
    });
  }

  /**
   * Add all overrides
   * @param {Content} content
   */
  overrides(contents) {
    var overrides = this._overrides;

    for (var rule in overrides) {
      if (overrides.hasOwnProperty(rule)) {
        contents.css(rule, overrides[rule].value, overrides[rule].priority);
      }
    }
  }

  /**
   * Adjust the font size of a rendition
   * @param {number} size
   */
  fontSize(size) {
    this.override("font-size", size);
  }

  /**
   * Adjust the font-family of a rendition
   * @param {string} f
   */
  font(f) {
    this.override("font-family", f, true);
  }

  destroy() {
    this.rendition = undefined;
    this._themes = undefined;
    this._overrides = undefined;
    this._current = undefined;
    this._injected = undefined;
  }
}

export default Themes;
